/**
 * mysql，该包依赖 XORM 封装了 MYSQL 的使用方法，本文件包含数据库操作引擎初始化逻辑
 * 创建者：Bryce
 * 创建时间：20210724
 */
package mysql

import (
	"fmt"
	"github.com/arthurkiller/rollingwriter"
	"github.com/go-playground/validator/v10"
	_ "github.com/go-sql-driver/mysql"
	"github.com/go-xorm/xorm"
	"time"
	"xorm.io/core"
)

// Config Mysql数据库配置结构体
type Config struct {
	Host            string `json:"host" validate:"required"`     // 数据库链接地址
	Port            string `json:"port"`                         // 数据库链接端口
	User            string `json:"user" validate:"required"`     // 数据库链接账号
	Pwd             string `json:"password" validate:"required"` // 数据库链接账号密码
	Name            string `json:"name" validate:"required"`     // 链接数据库名称
	Prefix          string `json:"prefix"`                       // 数据表前缀
	Charset         string `json:"charset"`                      // 数据库编码格式
	MaxOpen         int    `json:"max_open"`                     // 数据库最大允许打开链接数，默认 500
	MaxIdle         int    `json:"max_idle"`                     // 数据库最大允许空闲链接数，默认 10
	ConnMaxLifetime int64  `json:"conn_max_lifetime"`            // 最大连接时长，单位：秒
	Log             string `json:"log"`                          // 是否开启 SQL 日志记录，enable-启用（默认），disable-禁用
}

type GroupConfig struct {
	Master *Config   `json:"master" validate:"required"`
	Slaver []*Config `json:"slaver" validate:"omitempty"`
}

/**
 * BuildEngine，构建 MYSQL 操作引擎
 * Params：
 *   config：MYSQL 链接配置信息结构体
 * Return：
 *	MYSQL操作引擎
 */
func BuildEngine(config *Config) (engine *xorm.Engine, err error) {
	// 1. 配置验证
	if err = validator.New().Struct(config); err != nil {
		return nil, err
	}
	// 2. 设置默认值
	if config.Port == "" {
		config.Port = "3306"
	}
	if config.Charset == "" {
		config.Charset = "utf8mb4"
	}
	if config.MaxOpen == 0 {
		config.MaxOpen = 500
	}
	if config.MaxIdle == 0 {
		config.MaxIdle = 10
	}
	if config.Log != "disable" {
		config.Log = "enable"
	}
	// 3. 组装数据库链接信息，获取操作客户端
	if engine, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s",
		config.User, config.Pwd, config.Host, config.Port, config.Name, config.Charset)); err != nil {
		return
	}
	// 4. 设置表前缀
	if config.Prefix != "" {
		tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, config.Prefix)
		engine.SetTableMapper(tbMapper)
	}
	// 5. 设置连接池的最大连接数、空闲最大保持连接数
	engine.SetMaxOpenConns(config.MaxOpen)
	engine.SetMaxIdleConns(config.MaxIdle)
	if config.ConnMaxLifetime != 0 {
		engine.SetConnMaxLifetime(time.Duration(config.ConnMaxLifetime) * time.Second)
	} else {
		engine.SetConnMaxLifetime(28500)
	}
	// 6. 设置数据库时区为UTC
	utf, err := time.LoadLocation("UTC")
	if err != nil {
		return engine, err
	}
	engine.TZLocation = utf
	// 7. 使用 MYSQL 客户端 ping 测试链接是否有效
	if err = engine.Ping(); err != nil {
		return
	}
	// 8. 判断是否禁用 SQL 日志记录
	if config.Log == "disable" {
		return
	}
	// 9. 记录 SQL 日志
	logger, err := getLog()
	if err != nil {
		return engine, err
	}
	engine.SetLogger(logger)
	engine.ShowSQL(true)
	return
}

/**
 * BuildEngineGroup，构建 MYSQL 主从数据库操作引擎组
 * Params：
 *   configs：MYSQL 主从数据库链接配置信息，第一条配置信息为主库，其余都为从库
 * Return：
 *	MYSQL 主从操作引擎组
 */
func BuildEngineGroup(config *GroupConfig) (engineGroup *xorm.EngineGroup, err error) {
	// 1. 配置验证
	if err = validator.New().Struct(config); err != nil {
		return nil, err
	}
	configs := make([]*Config, 0)
	configs = append(configs, config.Master)
	configs = append(configs, config.Slaver...)
	// 2. 遍历配置，获取主从链接信息
	conn := make([]string, 0)
	for _, v := range configs {
		if v.Port != "" {
			v.Port = "3306"
		}
		if v.Charset == "" {
			v.Charset = "utf8mb4"
		}
		info := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s;", v.User, v.Pwd, v.Host, v.Port, v.Name, v.Charset)
		conn = append(conn, info)
	}
	// 3. 创建引擎组
	engineGroup, err = xorm.NewEngineGroup("mysql", conn, xorm.LeastConnPolicy())
	if err != nil {
		return
	}
	// 4. 设置默认值
	if configs[0].MaxOpen == 0 {
		engineGroup.SetMaxOpenConns(500)
	} else {
		engineGroup.SetMaxOpenConns(configs[0].MaxOpen)
	}
	if configs[0].MaxIdle == 0 {
		engineGroup.SetMaxIdleConns(500)
	} else {
		engineGroup.SetMaxIdleConns(configs[0].MaxIdle)
	}
	if configs[0].Prefix != "" {
		tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, configs[0].Prefix)
		engineGroup.SetTableMapper(tbMapper)
	}

	if configs[0].ConnMaxLifetime > 0 {
		engineGroup.SetConnMaxLifetime(time.Duration(configs[0].ConnMaxLifetime) * time.Second)
	} else {
		engineGroup.SetConnMaxLifetime(28500)
	}

	// 5. 设置数据库时区为UTC
	utf, err := time.LoadLocation("UTC")
	if err != nil {
		return engineGroup, err
	}
	engineGroup.TZLocation = utf
	// 6. 使用 MYSQL 客户端 ping 测试链接是否有效
	if err = engineGroup.Ping(); err != nil {
		return
	}
	// 7. 判断是否禁用 SQL 日志记录
	if configs[0].Log == "disable" {
		return
	}
	// 8. 记录 SQL 日志
	logger, err := getLog()
	if err != nil {
		return engineGroup, err
	}
	engineGroup.SetLogger(logger)
	engineGroup.ShowSQL(true)
	return
}

// SQL 引擎加入日志记录
func getLog() (*xorm.SimpleLogger, error) {
	// 1. 设置 sql 日志记录配置
	fileName := "sql-" + time.Now().Format("20060102")
	config := rollingwriter.Config{
		LogPath:            "./logs",                    //日志路径
		TimeTagFormat:      "20060102150405",            //时间格式串
		FileName:           fileName,                    //日志文件名
		MaxRemain:          3,                           //日志最大存留数
		RollingPolicy:      rollingwriter.VolumeRolling, // 滚动策略，这个设置按照日志文件大小滚动
		RollingTimePattern: "* * * * * *",               //时间滚动策略
		RollingVolumeSize:  "10M",                       //截断文件下限大小，设置为 10M
		WriterMode:         "none",                      // 日志写入模式,none/lock/async/buffer
		Compress:           true,                        // 是否使用 gzip 压缩日志文件
	}
	// 2. 实例化日志记录
	writer, err := rollingwriter.NewWriterFromConfig(&config)
	if err != nil {
		return nil, err
	}
	// 3. 注入日志记录配置
	logger := xorm.NewSimpleLogger(writer)
	return logger, nil
}
